﻿using System;
using System.Linq;
using System.Windows.Interactivity;
using System.Windows.Controls;
using System.Windows;
using System.Collections.Specialized;

namespace LogoFX.Controls.Behaviors
{
    public sealed class AutoscrollableBehavior : Behavior<ListBox>
    {
        #region Fields

        private static readonly Type s_thisType = typeof(AutoscrollableBehavior);

        private readonly NotifyCollectionChangedEventHandler _notifyCollectionChangedEventHandler;

        private readonly SizeChangedEventHandler _sizeChangedEventHandler;

        #endregion

        #region Constructors

        public AutoscrollableBehavior()
        {
            _notifyCollectionChangedEventHandler = new NotifyCollectionChangedEventHandler(OnCollectionChanged);
            _sizeChangedEventHandler = new SizeChangedEventHandler(OnSizeChanged);
        }

        #endregion

        #region Dependency Properties

        public static readonly DependencyProperty IsAutoscrollProperty =
            DependencyProperty.Register(
                "IsAutoscroll",
                typeof(bool),
                s_thisType,
                new PropertyMetadata(
                    true,
                    IsAutoscrollProperty_Changed));

        #endregion

        #region Public Properties

        public bool IsAutoscroll
        {
            get { return (bool)GetValue(IsAutoscrollProperty); }
            set { SetValue(IsAutoscrollProperty, value); }
        }

        #endregion

        #region Overrides 

        protected override void OnAttached()
        {
            AssociatedObject.Unloaded += (s, e) => Detach();
            Update(IsAutoscroll);
        }

        protected override void OnDetaching()
        {
            Update(false);
        }

        #endregion

        #region Private Members

        private void Update(bool isAutoscroll)
        {
            ItemCollection items = AssociatedObject.Items;
            INotifyCollectionChanged data = (INotifyCollectionChanged)items.SourceCollection;
            if (isAutoscroll)
            {
                data.CollectionChanged += _notifyCollectionChangedEventHandler;
                AssociatedObject.SizeChanged += _sizeChangedEventHandler;
                if (items.Count > 0)
                {
                    object selectedItem = items.OfType<object>().Last();
                    ScrollInfoView(items, selectedItem);
                }
            }
            else
            {
                AssociatedObject.SizeChanged -= _sizeChangedEventHandler;
                data.CollectionChanged -= _notifyCollectionChangedEventHandler;
            }
        }

        private static void IsAutoscrollProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            AutoscrollableBehavior behavior = (AutoscrollableBehavior)d;
            behavior.Update(behavior.IsAutoscroll);
        }

        private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            ListBox listBox = AssociatedObject;
            ItemCollection items = listBox.Items;

            object selectedItem = null;

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                case NotifyCollectionChangedAction.Move: 
                    selectedItem = e.NewItems[e.NewItems.Count - 1]; 
                    break;

                case NotifyCollectionChangedAction.Remove: 
                    
                    if (items.Count < e.OldStartingIndex)
                    {
                        selectedItem = items[e.OldStartingIndex - 1];
                    } 
                    else if (items.Count > 0)
                    {
                        selectedItem = items[0];
                    } 
                    break;

                case NotifyCollectionChangedAction.Reset: 
                    if (items.Count > 0)
                    {
                        selectedItem = items[0];
                    } 
                    break;
            }

            ScrollInfoView(items, selectedItem);
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            ListBox listBox = AssociatedObject;
            ItemCollection items = listBox.Items;

            ScrollInfoView(items, items.CurrentItem);
        }

        private void ScrollInfoView(ItemCollection items, object selectedItem)
        {
            if (ReferenceEquals(selectedItem, null))
            {
                return;
            }

            items.MoveCurrentTo(selectedItem);
            AssociatedObject.ScrollIntoView(selectedItem);
        }

        #endregion
    }
}